package com.nulldev.util.graphics.renderIt.renderer.swing.nitrous2.renderers;

import java.awt.*;
import java.awt.peer.ComponentPeer;
import java.lang.reflect.Method;

import com.nulldev.util.JVM.JVM;
import com.nulldev.util.JVM.reflections.ClassCache;
import com.nulldev.util.graphics.renderIt.renderer.swing.nitrous2.Renderer;
import com.nulldev.util.graphics.renderIt.renderer.swing.smoothCanvas.ComponentPeerTL;

import sun.java2d.SunGraphics2D;
import sun.java2d.SurfaceData;

/**
 * Represents a renderer using OpenGL and the the X Window System API.
 *
 * @see <a href="https://en.wikipedia.org/wiki/X_Window_System">X Window
 *      System</a>
 */
public class X11GLRenderer extends Renderer {
	/** Class name of the surface's peer component. */
	private final static String PEER_CLASS_NAME = "sun.awt.X11ComponentPeer";
	/** Class name of the surface to render on. */
	private final static String SURFACE_CLASS_NAME = "sun.java2d.opengl.GLXSurfaceData";

	/**
	 * Constructs a new X11GLRenderer.
	 *
	 * @param peer Component that the surface is displayed on. (e.g. An instance of
	 *             java.awt.Panel)
	 *
	 * @throws ClassNotFoundException If the peer or surface classes cannot be
	 *                                found. This will occur if the JRE/JDK of this
	 *                                machine doesn't support this renderer.
	 */
	public X11GLRenderer(final ComponentPeer peer) throws ClassNotFoundException {
		super(peer, PEER_CLASS_NAME, SURFACE_CLASS_NAME);
	}

	@Override
	public GraphicsConfiguration getGraphicsConfig() {
		if (!isSupported()) {
			return super.getGraphicsConfig();
		}

		try {
			final GraphicsConfiguration currentGraphicsConfiguration = super.getGraphicsConfig();

			final Class<?> x11GraphicsDevice = ClassCache.forName("sun.awt.X11GraphicsDevice");
			final Class<?> x11GraphicsConfig = ClassCache.forName("sun.awt.X11GraphicsConfig");

			final Method getVisualMethod = x11GraphicsConfig.getMethod("getVisual");
			final int visual = (int) getVisualMethod.invoke(currentGraphicsConfiguration);

			final Method getConfigMethod = x11GraphicsConfig.getMethod("getConfig", x11GraphicsDevice, int.class, int.class, int.class, boolean.class);

			// I pulled these values out of nowhere, may be wrong.
			// 24 = depth, 0 = colormap, false= doublebuffer
			return (GraphicsConfiguration) getConfigMethod.invoke(null, currentGraphicsConfiguration.getDevice(), visual, 24, 0, false);
		} catch (final Exception e) {
			// todo Tons of exceptions can be thrown in the above code, catch it or
			// something.
			// Otherwise, we fall back to the default and hope for the best.
			e.printStackTrace();
			return super.getGraphicsConfig();
		}
	}

	@Override
	protected Graphics2D createGraphics() {
		try {
			// Creates the surface.
			final Method createDataMethod = surfaceClass.getDeclaredMethod("createData", peerClass);
			final SurfaceData surfaceData = (SurfaceData) createDataMethod.invoke(null,
					(parent instanceof ComponentPeerTL ? ((ComponentPeerTL) parent).getTruePeer() : parent));

			// Fail if the surface is not created.
			if (surfaceData == null) {
				return null;
			}

			// Use unofficial APi to convert the surface to Graphics2D.
			return new SunGraphics2D(surfaceData, Color.BLACK, Color.BLACK, null);
		} catch (Exception e) {
			JVM.errorln(e);
			return null;
		}
	}

	/**
	 * Retrieves this renderer's name.
	 *
	 * @return This renderer's name.
	 */
	@Override
	public String getName() {
		return "OpenGL (X11)";
	}

	/**
	 * Determines whether the X11GLRenderer is supported on this machine.
	 *
	 * @return Whether the X11GLRenderer is supported on this machine.
	 */
	public static boolean isSupported() {
		return Renderer.isSupported(PEER_CLASS_NAME, SURFACE_CLASS_NAME);
	}
}